﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using n = EnnErrE.Utils.Numbers;

namespace EnnErrE.BillFiller
{
    /// <summary>
    /// Necessario affinch&eacute; un oggetto sia inseribile all'interno di un bollettino.
    /// </summary>
    public interface ICompilable
    {
        /// <summary>
        /// Richiamato in fase di compilazione del bollettino per consentire la compilazione dell'oggetto.
        /// </summary>
        /// <param name="graphics">Canovaccio grafico su cui compilare.</param>
        /// <param name="font">Font utilizzato per compilare.</param>
        /// <param name="text">Valore da inserire nel campo da compilare.</param>
        void Compile(Graphics graphics, Font font, string text);
    }
    /// <summary>
    /// Campo generico di un bollettino
    /// </summary>
    public abstract class BillField : ICompilable
    {
        /// <summary>
        /// Mantiene le informazioni sul dato da visualizzare nel campo.
        /// </summary>
        private string content;
        /// <summary>
        /// Mantiene la posizione del campo sul bollettino.
        /// </summary>
        private PointF location;
        /// <summary>
        /// Mantiene le dimensioni del campo sul bollettino.
        /// </summary>
        private SizeF size;
        /// <summary>
        /// Imposta/Restituisce la posizione del campo sul bollettino.
        /// </summary>
        public PointF Location { get { return location; } set { location = value; } }
        /// <summary>
        /// Imposta/Restituisce la dimensione del campo sul bollettino.
        /// </summary>
        public SizeF Size { get { return size; } set { size = value; } }
        /// <summary>
        /// Imposta/Restituisce le informazioni sul dato da visualizzare nel campo.
        /// </summary>
        public string Content { get { return content; } set { content = value; } }
        /// <summary>
        /// Costruttore di default.
        /// </summary>
        private BillField() : this(string.Empty, PointF.Empty, SizeF.Empty) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Infomazioni sul dato da visualizzare.</param>
        /// <param name="x">Posizione X sul bollettino.</param>
        /// <param name="y">Posizione Y sul bollettino.</param>
        /// <param name="width">Larghezza del campo sul bollettino.</param>
        /// <param name="height">Altezza del campo sul bollettino.</param>
        public BillField(string content, float x, float y, float width, float height) : this(content, new PointF(x, y), new SizeF(width, height)) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Informazioni sul dato da visualizzare.</param>
        /// <param name="location">Posizione del campo sul bollettino.</param>
        /// <param name="size">Dimensioni del campo sul bollettino.</param>
        public BillField(string content, PointF location, SizeF size)
        {
            Location = location;
            Size = size;
            Content = content;
        }
        /// <summary>
        /// Converte millimetri in punti.
        /// </summary>
        /// <param name="mm">Millimetri da convertire.</param>
        /// <param name="dpi">Risoluzione dot x inch (punti per pollice) del dispositivo grafico di riferimento.</param>
        /// <returns>Il risultato della conversione.</returns>
        protected float mm2px(float mm, float dpi) { return (int)Math.Round(n.Convert.mm2inch(mm) * dpi); }
        /// <summary>
        /// Compila il campo su un canovaccio grafico.
        /// </summary>
        /// <param name="graphics">Canovaccio grafico su cui disegnare.</param>
        /// <param name="font">Font utilizzato per scrivere i dati.</param>
        /// <param name="text">Testo da scrivere.</param>
        public abstract void Compile(Graphics graphics, Font font, string text);
    }
    /// <summary>
    /// Modalit&agrave; di ridimensionamento del testo
    /// </summary>
    public enum SizeMode
    {
        Trim,
        Resize,
        Multiline
    }
    /// <summary>
    /// Campo testuale.
    /// </summary>
    public class BillTextLine : BillField
    {
        /// <summary>
        /// Memorizza la modalit&agrave; di ridimensionamento del testo.
        /// </summary>
        private SizeMode sizeMode;
        /// <summary>
        /// Memorizza lo spiazzamento tra due linee consecutive.
        /// </summary>
        private float lineDeplacement;
        /// <summary>
        /// Imposta/Restituisce la modalit&agrave; di ridimensionamento del testo.
        /// </summary>
        public SizeMode SizeMode { get { return sizeMode; } set { sizeMode = value; } }
        /// <summary>
        /// Imposta/Restituisce lo spiazzamento tra due linee consecutive di testo.
        /// </summary>
        public float LineDeplacement { get { return lineDeplacement; } set { lineDeplacement = value; } }
        /// <summary>
        /// Costruttore di default.
        /// </summary>
        private BillTextLine() : this(string.Empty, PointF.Empty, SizeF.Empty, SizeMode.Trim) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="x">Ascissa da cui cominciare a scrivere.</param>
        /// <param name="y">Ordinata da cui cominciare a scrivere.</param>
        /// <param name="width">Larghezza del campo nel bollettino.</param>
        /// <param name="height">Altezza del campo nel bollettino.</param>
        public BillTextLine(string content, float x, float y, float width, float height) : this(content, new PointF(x, y), new SizeF(width, height), SizeMode.Trim) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="x">Ascissa da cui cominciare a scrivere.</param>
        /// <param name="y">Ordinata da cui cominciare a scrivere.</param>
        /// <param name="width">Larghezza del campo nel bollettino.</param>
        /// <param name="height">Altezza del campo nel bollettino.</param>
        /// <param name="lineDeplacement">Spiazzamento tra due linee consecutive sul bollettino.</param>
        public BillTextLine(string content, float x, float y, float width, float height, float lineDeplacement)
            : this(content, new PointF(x, y), new SizeF(width, height), SizeMode.Multiline)
        {
            LineDeplacement = lineDeplacement;
        }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="location">Punto da cui cominciare a scrivere.</param>
        /// <param name="size">Dimensioni del campo nel bollettino.</param>
        /// <param name="sizeMode">Modalit&agrave; di ridimensionamento del testo.</param>
        public BillTextLine(string content, PointF location, SizeF size, SizeMode sizeMode)
            : base(content, location, size)
        {
            SizeMode = sizeMode;
            lineDeplacement = 0;
        }
        /// <summary>
        /// Compila il campo di testo.
        /// </summary>
        /// <param name="g">Canovaccio grafico su cui disegnare.</param>
        /// <param name="f">Font utilizzato per il disegno.</param>
        /// <param name="text">Testo da disegnare sul canovaccio.</param>
        public override void Compile(Graphics g, Font f, string text)
        {
            StringFormat sf = new StringFormat();
            sf.Trimming = StringTrimming.EllipsisCharacter;
            SizeF fs = g.MeasureString(text, f);
            RectangleF rect = new RectangleF(mm2px(Location.X, g.DpiX), mm2px(Location.Y, g.DpiY) - fs.Height, mm2px(Size.Width, g.DpiX), fs.Height);
            g.DrawString(text, f, Brushes.Black, rect, sf);
        }
    }
    /// <summary>
    /// Campo testuale in box.
    /// </summary>
    public class BillBoxedText : BillField
    {
        /// <summary>
        /// Memorizza la modalita' di scrittura multilinea.
        /// </summary>
        private float lineDeplacement;
        /// <summary>
        /// Memorizza la lunghezza del campo.
        /// </summary>
        private int length;
        /// <summary>
        /// Imposta/Restituisce la lunghezza del campo.
        /// </summary>
        public int Length { get { return length; } set { length = value; } }
        /// <summary>
        /// Specifica se il campo deve estendersi su due linee.
        /// </summary>
        public bool MultiLine { get { return lineDeplacement > 1e-10; } }
        /// <summary>
        /// Imposta/Restituisce la spaziatura tra le linee di testo.
        /// </summary>
        public float LineDeplacement { get { return lineDeplacement; } set { lineDeplacement = value; } }
        /// <summary>
        /// Costruttore di default.
        /// </summary>
        private BillBoxedText() : this(string.Empty, PointF.Empty, SizeF.Empty, 0) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="x">Ascissa da cui cominciare a scrivere.</param>
        /// <param name="y">Ordinata da cui cominciare a scrivere.</param>
        /// <param name="width">Larghezza del campo nel bollettino.</param>
        /// <param name="height">Altezza del campo nel bollettino.</param>
        public BillBoxedText(string content, float x, float y, float width, float height) : this(content, new PointF(x, y), new SizeF(width, height), 10) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="location">Angolo superiore sinistro del primo box in cui scrivere.</param>
        /// <param name="size">Larghezza di ogni singolo box.</param>
        /// <param name="length">Lunghezza totale del campo.</param>
        public BillBoxedText(string content, PointF location, SizeF size, int length)
            : this(content, location, size, length, 0) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="location">Angolo superiore sinistro del primo box in cui scrivere.</param>
        /// <param name="size">Larghezza di ogni singolo box.</param>
        /// <param name="length">Lunghezza totale del campo.</param>
        /// <param name="lineDeplacement">Distanza tra due righe di testo.</param>
        public BillBoxedText(string content, PointF location, SizeF size, int length, float lineDeplacement)
            : base(content, location, size)
        {
            LineDeplacement = Math.Abs(lineDeplacement);
            Length = length;
        }
        protected void DrawLine(Graphics graphics, string text, Font f, RectangleF place, float spacing)
        {
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
            int len = Math.Min(text.Length, Length);
            for (int index = 0; index < len; ++index)
            {
                graphics.DrawString(text[index].ToString(), f, Brushes.Black, place, sf);
                place.Offset(place.Width + spacing, 0);
            }
        }
        /// <summary>
        /// Stampa i singoli caratteri nei box.
        /// </summary>
        /// <param name="graphics">Canovaccio grafico su cui disegnare.</param>
        /// <param name="text">Testo da scrivere.</param>
        /// <param name="f">Font utilizzato per scrivere.</param>
        /// <param name="place">Angolo superiore sinistro del primo box.</param>
        /// <param name="spacing">Spaziatura tra i box.</param>
        protected void DrawBoxedText(Graphics graphics, string text, Font f, RectangleF place, float spacing)
        {
            text = text.ToUpper();
            DrawLine(graphics, text, f, place, spacing);
            if (MultiLine && (text.Length > Length))
            {
                place.Offset(0, mm2px(LineDeplacement, graphics.DpiY));
                DrawLine(graphics, text.Substring(Length), f, place, spacing);
            }
        }
        /// <summary>
        /// Compila il campo.
        /// </summary>
        /// <param name="g">Canovaccio grafico su cui disegnare.</param>
        /// <param name="f">Font utilizzato per il disegno.</param>
        /// <param name="text">Testo da disegnare sul canovaccio.</param>
        public override void Compile(Graphics g, Font f, string text)
        {
            DrawBoxedText(g, text, f, new RectangleF(mm2px(Location.X, g.DpiX), mm2px(Location.Y, g.DpiY), mm2px(Size.Width, g.DpiX), mm2px(Size.Height, g.DpiY)), 0.46f);
        }
    }
    /// <summary>
    /// Campo numero in box.
    /// </summary>
    public class BillNumber : BillBoxedText
    {
        /// <summary>
        /// Memorizza il numero di cifre decimali.
        /// </summary>
        private decimal decimals;
        /// <summary>
        /// Memorizza lo spiazzamento relativo alla virgola.
        /// </summary>
        private float commaDeplacement;
        /// <summary>
        /// Imposta/Restituisce il numero di cifre decimali.
        /// </summary>
        public decimal Decimals { get { return decimals; } set { decimals = value; } }
        /// <summary>
        /// Imposta/Restituisce lo spiazzamento relativo alla virgola.
        /// </summary>
        public float CommaDeplacement { get { return commaDeplacement; } set { commaDeplacement = value; } }
        /// <summary>
        /// Costruttore di default.
        /// </summary>
        private BillNumber() : this(string.Empty, PointF.Empty, SizeF.Empty, 0, 2, 0.2f) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="x">Ascissa da cui cominciare a scrivere.</param>
        /// <param name="y">Ordinata da cui cominciare a scrivere.</param>
        /// <param name="width">Larghezza del campo nel bollettino.</param>
        /// <param name="height">Altezza del campo nel bollettino.</param>
        /// <param name="decimals">Numero di cifre decimali.</param>
        /// <param name="commaDeplacement">Spiazzamento relativo alla virgola.</param>
        public BillNumber(string content, float x, float y, float width, float height, int length, int decimals, float commaDeplacement) :
            this(content, new PointF(x, y), new SizeF(width, height), length, decimals, commaDeplacement) { }
        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="content">Nome del campo associato.</param>
        /// <param name="location">Punto da cui cominciare a scrivere.</param>
        /// <param name="size">Dimensioni del campo nel bollettino.</param>
        /// <param name="length">Lunghezza del testo.</param>
        /// <param name="decimals">Numero di cifre decimali.</param>
        /// <param name="commaDeplacement">Spiazzamento relativo alla virgola.</param>
        public BillNumber(string content, PointF location, SizeF size, int length, int decimals, float commaDeplacement) :
            base(content, location, size, length)
        {
            Decimals = decimals;
            CommaDeplacement = commaDeplacement;
        }
        /// <summary>
        /// Compila il campo.
        /// </summary>
        /// <param name="g">Canovaccio grafico su cui disegnare.</param>
        /// <param name="f">Font per scrivere il testo.</param>
        /// <param name="text">Testo da scrivere.</param>
        public override void Compile(Graphics g, Font f, string text)
        {
            int decimals = (int)Decimals; // numero di cifre decimali
            int len = Length - decimals; // lunghezza totale del campo
            float value = float.Parse(text); // valore da stampare
            int integral = (int)value; // parte intera da stampare
            float dec = value - integral; // parte decimale da stampare
            // box nel quale stampare
            RectangleF box = new RectangleF(mm2px(Location.X, g.DpiX), mm2px(Location.Y, g.DpiY), mm2px(Size.Width, g.DpiX), mm2px(Size.Height, g.DpiY));
            // stampa la parte intera
            DrawBoxedText(g, integral.ToString().PadLeft(len), f, box, 0.2f);
            // sposta il box per la parte decimale
            box.Offset(mm2px(Size.Width, g.DpiX) * len + mm2px(commaDeplacement, g.DpiX), 0);
            // stampa la parte decimale
            DrawBoxedText(g, dec.ToString().PadRight(decimals, '0'), f, box, 0.2f);
        }
    }
}
